// Copyright (c) Microsoft
// All rights reserved
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABLITY OR NON-INFRINGEMENT.
// See the Apache Version 2.0 License for specific language governing permissions and limitations under the License.
#pragma once
/**********************************************************************************
* amp.compare.h
* 
*
**********************************************************************************/

#include <amp.h>
#include <amp_math.h>
#include <vector>
//#include <amptest/logging.h>
//#include <amptest/math.h>
//#include <amptest/compare.h>
//#include <amptest/operators.h>


namespace Concurrency
{
    namespace Test
    {
        // The maximum number of incorrect elements to log before just returning the summary
        static const size_t max_failed_elements_to_log = 20;

        // Details namespace serves as private namespace
        namespace details 
        {
            // TODO: These AreEqual (and the AreAlmostEqual) functions are now obsolete. Use a type_comparer<T> instead as
            // it provides the 'are almost equal' semantics by default.
            // The only place where this function's used (but shouldn't be) is in Functional\Language\VectorSubset\GeneralUtilitiesLibrary\Pointer\*
            // because each test includes Pointer\inc\common.h which uses these functions. :-(
            // Once those are cleaned up, then these AreEqual and AreAlmostEqual functions can be deleted.

            template<typename T, typename Tpredicate>
            bool Verify_impl(const T* ary_actual, const T* ary_expected, size_t ary_length, const Tpredicate& pred)
            {
                size_t num_failed = 0;
                for(size_t i = 0; i < ary_length; ++i)
                {
                    if (!pred(ary_expected[i], ary_actual[i]))
                    {
                        num_failed++;

                        if(num_failed == 1)
                        {
                            //Log(LogType::Error) << "Verify found elements with incorrect values: " << std::endl;
                        }

                        if(num_failed <= max_failed_elements_to_log)
                        {
                            //Log(LogType::Error) << "   " << compose_incorrect_element_message(i, ary_expected[i], ary_actual[i]) << std::endl;
                        }
                        else if (num_failed == max_failed_elements_to_log+1) 
                        {
                            //Log(LogType::Error) << "      and more..." << std::endl;
                        }
                    }
                }

                if(num_failed != 0) {
                    //Log(LogType::Error) << "      " << num_failed << " out of " << ary_length << " elements failed." << std::endl;
                }
                return num_failed == 0;
            }
        }

        // Compare arrays for arbitrary type, T is required to define operator<
        // Returns 'true' if arrays contain same results and 'false' otherwise.
        // Start of Verify functions for c-style arrays
        template<typename T>
        bool Verify(const T *c, const T *refc, size_t size, type_comparer<T> comparer = type_comparer<T>())
        {
            return details::Verify_impl(c, refc, size, [=](T exp, T act)
            {
                // Need to wrap with lambda so the char/short overloads can be picked by the compiler
                return comparer.are_equal(exp, act);
            });
        }

        // End of Verify functions for c-style arrays

        // Start of Verify functions for std::vector
        template<typename T>
        bool Verify(const std::vector<T> &c, const std::vector<T> &refc, type_comparer<T> comparer = type_comparer<T>())
        {
            if (c.size() != refc.size()) { return false; }
            return Verify(c.data(), refc.data(), refc.size(), comparer);
        }

        // This overload just simplifies when wanting to control the options when comparing floating-point types
        template<typename T, typename Tmax_args>
        bool Verify( const std::vector<T> &c, const std::vector<T> &refc, const Tmax_args maxAbsoluteDiff, const Tmax_args maxRelativeDiff)
        {
            static_assert(std::is_floating_point<Tmax_args>::value, "The args passed in for the limits should be a floating point type (i.e. both float or both double)");

            // This enforces that T must be float/double since (currently) they're the only type_comparers
            // that provide this ctor.
            type_comparer<T> comparer(static_cast<T>(maxAbsoluteDiff), static_cast<T>(maxRelativeDiff));
            return Verify(c, refc, comparer);
        }
        // End of Verify functions for c-style arrays

        #pragma region VerifyDataOnCpu()

        // Verifies that data containes in the supplied C++ AMP container and standard container differs by value 'diff'. 
        // The computation happens on CPU. If the supplied array have data on GPU, it will get copied on CPU.
        template<typename _type, int _rank,	template<typename, int> class _amp_container_type, template<typename T, typename=std::allocator<T>> class _stl_cont>
        bool VerifyDataOnCpu(const _stl_cont<_type>& actual, const _amp_container_type<_type, _rank>& expected, _type diff = 0)
        {
            if(expected.get_extent().size() != actual.size())
            {
                //Log(LogType::Error) << "Size of expected and actual does not match.\n";
                //Log(LogType::Error) << "Size of actual: " << actual.size();
                //Log(LogType::Error) << "Size of expected: " << expected.get_extent().size();
                return false;
            }

            std::vector<_type> temp_cont(expected.get_extent().size());
            copy(expected, temp_cont.begin());

            return Equal(actual.begin(), actual.end(), temp_cont.begin(), Difference<_type>(diff));
        }
#pragma endregion

    }
}


